package nl.Viewer.ViewManager.Views;

import java.nio.Buffer;
import java.util.concurrent.ConcurrentHashMap;

import javax.media.opengl.GL;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.GLCapabilities;
import javax.media.opengl.GLEventListener;
import javax.media.opengl.GLJPanel;
import javax.media.opengl.glu.GLU;

import nl.Viewer.SerializedObjects.Parameter;
import nl.Viewer.ViewManager.Controllers.GenericController;

/**
 * This is the implementation of a View to show the outlines of an image with the outlines of the visible selection as
 * well
 * 
 * @author Jeroen Verschuur
 * 
 */

public class JOGLImageOrientationView extends GenericView implements GLEventListener {
   private static final long serialVersionUID        = -2958949544243327703L;

   /********************************************************************************************************************
    * 
    * Interaction properties
    * 
    */

   /* viewing parameters */

   protected Parameter       VisibleRectangle;
   protected double          visibleRectangleValue[] = new double[] { 0.0, 0.0, 1.0, 1.0 };
   protected Parameter       ImageDimension;
   protected int             imageDimensionValue[]   = new int[] { 0, 0 };

   private boolean           parametersRetrieved     = false;

   /********************************************************************************************************************
    * 
    */

   protected int             panelWidth              = 0, panelHeight = 0;
   protected Double          aspect                  = 1.0;
   protected boolean         interacting             = false;
   protected GLU             glu                     = new GLU();
   protected Buffer          buf                     = null;

   // Boolean object to be able to synchronize on the object
   private Boolean           isRedisplaying          = false;

   private static int        inset                   = 4;
   protected double          scale                   = 1.0;
   protected int             centerX                 = 0;
   protected int             centerY                 = 0;

   public JOGLImageOrientationView( GenericController parent ) {
      super( parent );
      this.renderPanel = new GLJPanel( getCapabilities() );

      parent.getDataModel().addDataStoreModelChangeListener( this );
      parent.getDataModel().addParameterModelChangeListener( this );

      ( (GLJPanel)renderPanel ).addGLEventListener( this );
   }

   public static GLCapabilities getCapabilities() {
      GLCapabilities c = new GLCapabilities();
      c.setDoubleBuffered( true );
      return c;
   }

   /**
    * Called by drawable to indicate mode or device has changed
    */
   public void displayChanged( GLAutoDrawable drawable, boolean modeChanged, boolean deviceChanged ) {
   }

   /**
    * Called after OpenGL is init'ed
    */
   public void init( GLAutoDrawable drawable ) {
      GL gl = drawable.getGL();
      // set erase color
      gl.glClearColor( 0.0f, 0.0f, 0.0f, 0.0f ); // white
      // set drawing color and point size
      gl.glColor3f( 0.0f, 0.0f, 0.0f );
      gl.glPointSize( 1.0f ); // a 'dot' is 4 by 4 pixel

      // enable alpha mask (import from gif sets alpha bits)
      gl.glBlendFunc( GL.GL_SRC_ALPHA, GL.GL_ONE_MINUS_SRC_ALPHA );
      gl.glEnable( GL.GL_BLEND );
   }

   /**
    * Called to indicate the drawing surface has been moved and/or resized
    */
   public void reshape( GLAutoDrawable drawable, int x, int y, int width, int height ) {
      GL gl = drawable.getGL();
      gl.glViewport( 0, 0, width, height );
      gl.glMatrixMode( GL.GL_PROJECTION );
      gl.glLoadIdentity();
      panelWidth = width;
      panelHeight = height;
      aspect = (double)panelWidth / (double)panelHeight;
      glu.gluOrtho2D( 0.0, width, 0.0, height );

      synchronizeParameterValues();
   }

   public void display() {
      ( (GLJPanel)renderPanel ).display();
   }

   /**
    * Called by drawable to initiate drawing
    */
   public void display( GLAutoDrawable drawable ) {
      GL gl = drawable.getGL();
      gl.glClear( GL.GL_COLOR_BUFFER_BIT );

      gl.glColor4f( 1, 0, 0, 1 );

      // draw two rectangles, the image bounds, and the visibleRectangle
      gl.glColor4f( 1, 1, 1, 1 );
      gl.glBegin( GL.GL_QUADS );
      gl.glVertex2d( centerX + ( visibleRectangleValue[0] * scale ), centerY + ( visibleRectangleValue[1] * scale ) );
      gl.glVertex2d( centerX + ( visibleRectangleValue[2] * scale ), centerY + ( visibleRectangleValue[1] * scale ) );
      gl.glVertex2d( centerX + ( visibleRectangleValue[2] * scale ), centerY + ( visibleRectangleValue[3] * scale ) );
      gl.glVertex2d( centerX + ( visibleRectangleValue[0] * scale ), centerY + ( visibleRectangleValue[3] * scale ) );
      gl.glEnd();

      gl.glBegin( GL.GL_LINE_LOOP );
      gl.glVertex2d( centerX - 1, centerY - 1 );
      gl.glVertex2d( centerX + 1 + ( imageDimensionValue[0] * scale ), centerY - 1 );
      gl.glVertex2d( centerX + 1 + ( imageDimensionValue[0] * scale ), centerY + 1 + ( imageDimensionValue[1] * scale ) );
      gl.glVertex2d( centerX - 1, centerY + 1 + ( imageDimensionValue[1] * scale ) );
      gl.glEnd();      
      
      gl.glFlush();
      ( (GLJPanel)renderPanel ).swapBuffers();
   }

   /*
    * Parameter methods
    * 
    */
   public void parameterValueChanged( Parameter p ) {
      synchronized( this ) {
         synchronizeParameterValues();
      }
   }

   public void dataStoreChanged( boolean newDataStore ) {
      synchronized( this ) {
         getAllParameters();
      }
   }

   private void getAllParameters() {
      if( parametersRetrieved )
         return;

      ConcurrentHashMap<String, Parameter> parameters = parent.getParameters();

      VisibleRectangle = parameters.get( "VisibleRectangle" );
      if( VisibleRectangle == null )
         System.out.println( this.getClass()
            + " expected parameter VisibleRectangle to be set by either the server or the associated image view" );

      ImageDimension = parameters.get( "ImageDimension" );
      if( ImageDimension == null )
         System.out.println( this.getClass()
            + " expected parameter ImageDimension to be set by either the server or the associated image view" );

      parametersRetrieved = true;
   }

   private void internalRedisplay() {
      synchronized( isRedisplaying ) {
         new Thread( new Runnable() {
            public void run() {
               display();
            }
         } ).start();
      }
   }

   private void synchronizeParameterValues() {
      if( VisibleRectangle != null ) {
         visibleRectangleValue = GenericView.objectToDoubleArray( VisibleRectangle.getParameterValue() );
      }
      if( ImageDimension != null ) {
         imageDimensionValue = GenericView.objectToIntArray( ImageDimension.getParameterValue() );

         // calculate new scale
         double horizontalScale = (double)( panelWidth - ( 2 * inset ) ) / (double)imageDimensionValue[0];
         double verticalScale = (double)( panelHeight - ( 2 * inset ) ) / (double)imageDimensionValue[1];

         scale = horizontalScale < verticalScale ? horizontalScale : verticalScale;

         centerX = ( panelWidth - (int)( (double)imageDimensionValue[0] * scale ) ) / 2;
         centerY = ( panelHeight - (int)( (double)imageDimensionValue[1] * scale ) ) / 2;

         internalRedisplay();
      }
      
      super.synchronizeViews();
   }
}
